/*
 * Copyright 2007-2009 Thomas Krause (thomas@berlin-reloaded.de)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gidoo.cms.jcr;

import java.io.IOException;
import java.util.Calendar;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.gidoo.cms.GidooCMS;
import org.gidoo.cms.eventhandler.OnDraftSubmittedEvent;
import org.gidoo.cms.eventhandler.OnPageDeletedEvent;
import org.gidoo.cms.eventhandler.OnPageMovedEvent;
import org.gidoo.cms.eventhandler.OnResourceDeletedEvent;
import org.gidoo.cms.helper.Tools;
import org.gidoo.cms.login.GidooCMSSession;

/**
 * Handle editing of pages.
 * @author <a href="mailto:thomas@berlin-reloaded.de">Thomas Krause</a>
 */
public class EditDataManager
{

  /**
   * This will create a new either public or private draft for a page.
   * @param page The page to update.
   * @param isPrivate True if this should be a private draft.
   * @param title The new title.
   * @param content The content.
   * @param s The session.
   * @return True if successful.
   */
  public static boolean createDraft(Node page, boolean isPrivate,
    String title, String content,
    GidooCMSSession s)
  {
    if(page == null || s == null)
    {
      return false;
    }

    try
    {
      Node rootNode = s.getJcrRootNode();
      String convertedPath = Tools.path2DraftName(page.getPath().substring(1));
      Node drafts = rootNode.getNode("drafts");
      Node privateDrafts = rootNode.getNode("private_drafts");
      // check for an old draft and remove it necessary
      if(drafts.hasNode(convertedPath))
      {
        Node oldDraft = drafts.getNode(convertedPath);
        oldDraft.remove();
      }
      if(privateDrafts.hasNode(convertedPath))
      {
        Node oldDraft = privateDrafts.getNode(convertedPath);
        oldDraft.remove();
      }

      // create new draft
      Node parent = isPrivate ? privateDrafts : drafts;

      Node newDraft = parent.addNode(convertedPath, page.getPrimaryNodeType().getName());

      newDraft.setProperty("cms:xml", content == null ? "" : content);
      newDraft.setProperty("cms:title", title == null ? "" : title);
      newDraft.setProperty("cms:editor", s.getUsername());
      newDraft.setProperty("cms:publisher", "");
      if(page.hasProperty("cms:created"))
      {
        newDraft.setProperty("cms:created", page.getProperty("cms:created").getDate());
      }
      else
      {
        newDraft.setProperty("cms:created", Calendar.getInstance());
      }
      newDraft.setProperty("cms:lastModified", Calendar.getInstance());

      parent.save();

      ((GidooCMS) s.getApplication()).getEventManager().fireEvent(
        new OnDraftSubmittedEvent(convertedPath, s.getUsername(), isPrivate));

      return true;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }
    return false;
  }

  /**
   * Add a new resource as draft.
   * @param fileUpload The model.
   * @param mime The mime type, may be null if mime-type should be autodetected.
   * @param pageNode The page to add this draft to.
   * @param s The session.
   * @return The new ressource or null if an error occured.
   */
  public static Node addResource(FileUpload fileUpload, String mime,
    Node pageNode, GidooCMSSession s)
  {
    try
    {
      if(fileUpload == null || pageNode == null || s == null)
      {
        return null;
      }

      String pagePath = pageNode.getPath().substring(1);

      if(mime == null || "".equals(mime))
      {
        mime = fileUpload.getContentType();
      }

      Node drafts = s.getJcrRootNode().getNode("drafts");

      Node res = drafts.addNode(Tools.path2DraftName(pagePath + "/" + fileUpload.getClientFileName()), "cms:resource");

      res.setProperty("jcr:mimeType", mime);
      res.setProperty("cms:editor", s.getUsername());
      res.setProperty("cms:size", fileUpload.getSize());
      res.setProperty("jcr:data", fileUpload.getInputStream());
      res.setProperty("jcr:lastModified", Calendar.getInstance());

      return res;
    }
    catch(IOException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("ioexception"));
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }

    return null;
  }

  /**
   * Delete a resource.
   * @param res The resource. (either published or draft).
   * @param s The session.
   * @return True if successful.
   */
  public static boolean deleteResource(Node res, GidooCMSSession s)
  {
    if(res == null || s == null)
    {
      return false;

    }
    try
    {
      String oldPath = res.getPath().substring(1);
      Node parent = res.getParent();
      if(parent.getName().equals("drafts"))
      {
        oldPath = Tools.draftName2Path(res.getName());
      }

      res.remove();
      parent.save();

      Logger.getLogger(GidooCMS.ACTION_LOG).log(Level.INFO,
        "DELETE ACTION: user \"" + s.getUsername() + "\" deleted " +
        "resource " + oldPath);

      ((GidooCMS) s.getApplication()).getEventManager().fireEvent(
            new OnResourceDeletedEvent(oldPath, s.getUsername()));

      return true;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }
    return false;
  }

  /**
   * Moves a page "up" in the order.
   * @param page The page.
   * @param s The session.
   * @return True if successful.
   */
  public static boolean pageUp(Node page, GidooCMSSession s)
  {
    if(page == null || s == null)
    {
      return false;
    }

    try
    {
      if("cms:section".equals(page.getPrimaryNodeType().getName()))
      {
        s.error(getI18N(s).getString("sectionnotallowed"));
        return false;
      }

      Node parent = page.getParent();
      NodeIterator it = parent.getNodes();

      Node lastNode = null;

      while(it.hasNext())
      {
        Node c = it.nextNode();
        if(c.getName().equals(page.getName()))
        {
          if(lastNode != null)
          {
            parent.orderBefore(page.getName(), lastNode.getName());
            parent.save();
          }
          break;
        }
        else if("cms:page".equals(c.getPrimaryNodeType().getName()))
        {
          lastNode = c;
        }
      }

      return true;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));

    }

    return false;
  }

  /**
   * Moves a page "down" in the order.
   * @param page The page.
   * @param s The session.
   * @return True if successful.
   */
  public static boolean pageDown(Node page, GidooCMSSession s)
  {
    try
    {
      if("cms:section".equals(page.getPrimaryNodeType().getName()))
      {
        s.error(getI18N(s).getString("sectionnotallowed"));
        return false;
      }

      Node parent = page.getParent();
      NodeIterator it = parent.getNodes();

      while(it.hasNext())
      {
        Node c = it.nextNode();
        if(c.getName().equals(page.getName()))
        {
          Node nextCMSNode = null;
          while(it.hasNext())
          {
            Node n = it.nextNode();
            if("cms:page".equals(n.getPrimaryNodeType().getName()))
            {
              nextCMSNode = n;
              break;
            }
          }
          if(nextCMSNode != null)
          {
            parent.orderBefore(nextCMSNode.getName(), page.getName());
            parent.save();
          }
          break;
        }
      }

      return true;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));

    }

    return false;
  }

  /**
   * Add a new page.
   * @param name The name of the new page.
   * @param parent The parent page.
   * @param s The session.
   * @return The new page. Null if an error occured.
   */
  public static Node addNewPage(String name, Node parent, GidooCMSSession s)
  {

    if(parent == null || s == null)
    {
      return null;
    }

    if(name != null && !"".equals(name))
    {
      try
      {

        // check for unallowed characters
        if(name.contains("/"))
        {
          s.error(getI18N(s).getString("noslashallowed"));
          return null;
        }
        if(JcrHelper.META_DATA_NODE_NAME.equals(name))
        {
          s.error(getI18N(s).getString("namereserved"));
          return null;
        }

        // check if already existant
        NodeIterator it = parent.getNodes();
        while(it.hasNext())
        {
          Node n = it.nextNode();
          if(n.getName().equals(name))
          {
            s.error(getI18N(s).getString("alreadyexists"));
            return null;
          }
        }

        // construct a new page
        Node p = parent.addNode(name, "cms:page");
        p.setProperty("cms:editor", s.getUsername());

        parent.save();

        return p;
      }
      catch(RepositoryException ex)
      {
        Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
        s.error(getI18N(s).getString("jcrerror"));
      }
    }
    else
    {
      s.error(getI18N(s).getString("namewasnull"));
    }

    return null;
  }

  /**
   * Delete an existing page.
   * @param page The page to delete.
   * @param s The session.
   * @return True if successful.
   */
  public static boolean deletePage(Node page, GidooCMSSession s)
  {
    if(page == null || s == null)
    {
      return false;
    }

    try
    {

      if("cms:section".equals(page.getPrimaryNodeType().getName()))
      {
        s.error(getI18N(s).getString("sectionnotallowed"));
      }
      // we always have at least one node (cms:md), so check if there are more
      else if(page.getNodes().getSize() > 1)
      {
        s.error(getI18N(s).getString("nosubpagesallowed"));
      }
      else
      {
        Node parent = page.getParent();

        boolean pageEmpty = true;
        if(page.hasProperty("cms:xml") &&
          !page.getProperty("cms:xml").getString().equals(""))
        {
          pageEmpty = false;
        }
        if(page.hasProperty("cms:title") &&
          !page.getProperty("cms:title").getString().equals(""))
        {
          pageEmpty = false;
        }
        if(pageEmpty)
        {
          String oldPath = page.getPath();
          page.remove();
          parent.save();

          Logger.getLogger(GidooCMS.ACTION_LOG).log(Level.INFO,
            "DELETE ACTION: user \"" + s.getUsername() +
            "\" deleted page " + oldPath);

          ((GidooCMS) s.getApplication()).getEventManager().fireEvent(
            new OnPageDeletedEvent(oldPath, s.getUsername()));

        }
        else
        {
          s.error(getI18N(s).getString("pagemustbeemptybeforedelete"));
        }
      }

      return true;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }

    return false;

  }

  /**
   * Rename a page.
   * @param page The page.
   * @param name Thew new name.
   * @param s The session.
   * @return The new page or null if an error occured.
   */
  public static Node renamePage(Node page, String newName, GidooCMSSession s)
  {
    if(page == null || s == null)
    {
      return null;
    }

    try
    {
      Node parent = page.getParent();

      if(newName == null || "".equals(newName))
      {
        s.error(getI18N(s).getString("namewasnull"));
      }
      else if("cms:section".equals(page.getPrimaryNodeType().getName()))
      {
        s.error(getI18N(s).getString("sectionnotallowed"));
      }
      else if(newName.contains("/"))
      {
        s.error(getI18N(s).getString("noslashallowed"));
      }
      else if(JcrHelper.META_DATA_NODE_NAME.equals(newName))
      {
        s.error(getI18N(s).getString("namereserved"));
      }
      else
      {
        // check if already existant
        NodeIterator it = parent.getNodes();
        while(it.hasNext())
        {
          Node n = it.nextNode();
          if(n.getName().equals(newName))
          {
            s.error(getI18N(s).getString("alreadyexists"));
            return null;
          }
        }

        String oldPath = page.getPath();
        Session session = s.getJcrSession();

        session.move(page.getPath(), parent.getPath() + "/" + newName);

        //find private and public drafts
        moveDrafts(session, oldPath, page.getPath());
        
        session.save();

        Logger.getLogger(GidooCMS.ACTION_LOG).log(Level.INFO,
          "RENAME ACTION: user \"" + s.getUsername() +
          "\" renamed page from " + oldPath + " to " + page.getPath());

        ((GidooCMS) s.getApplication()).getEventManager().fireEvent(
        new OnPageMovedEvent(oldPath, page.getPath(), s.getUsername(), true));

        return page;
      }

    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }

    return null;
  }

  /**
   * Move/rename the drafts for a page
   * @param session
   * @param oldPath Absolute path to old page
   * @param newPath Absolute path to new page
   * @throws javax.jcr.RepositoryException
   */
  private static void moveDrafts(Session session, String oldPath, String newPath)
    throws RepositoryException
  {
    Node rootNode = session.getRootNode();
    String convertedOldPath = Tools.path2DraftName(oldPath.substring(1));
    String convertedNewPath = Tools.path2DraftName(newPath.substring(1));
    Node drafts = rootNode.getNode("drafts");
    Node privateDrafts = rootNode.getNode("private_drafts");

    if(drafts.hasNode(convertedOldPath))
    {
      Node d = drafts.getNode(convertedOldPath);
      session.move(d.getPath(), drafts.getPath() + "/" + convertedNewPath);
    }

    if(privateDrafts.hasNode(convertedOldPath))
    {
      Node d = privateDrafts.getNode(convertedOldPath);
      session.move(d.getPath(), privateDrafts.getPath() + "/" + convertedNewPath);
    }
  }

  /**
   * Move a page.
   * @param page The page to move.
   * @param newParent The new parent.
   * @param s The session.
   * @return The new page or null if an error occured.
   */
  public static Node movePage(Node pageToMove, Node newParent, GidooCMSSession s)
  {
    if(pageToMove == null || newParent == null || s == null)
    {
      return null;
    }

    try
    {
      if("cms:section".equals(pageToMove.getPrimaryNodeType().getName()))
      {
        s.error(getI18N(s).getString("sectionnotallowed"));
        return null;
      }

      Node oldSection = JcrHelper.getSectionOfNode(pageToMove);
      Node newSection = JcrHelper.getSectionOfNode(newParent);


      if(!oldSection.getName().equals(newSection.getName()))
      {
        s.error(getI18N(s).getString("nooverlappaste"));
        return null;
      }

      if(pageToMove.getParent().equals(newParent))
      {
        return pageToMove;
      }

      String oldPath = pageToMove.getPath();

      Session session = s.getJcrSession();

      session.move(pageToMove.getPath(), newParent.getPath() + "/" + pageToMove.getName());

      //find private and public drafts
      moveDrafts(session, oldPath, pageToMove.getPath());

      session.save();

      Logger.getLogger(GidooCMS.ACTION_LOG).log(Level.INFO,
        "MOVE ACTION: user \"" + s.getUsername() +
        "\" moved page " + oldPath + " to " + pageToMove.getPath());

      ((GidooCMS) s.getApplication()).getEventManager().fireEvent(
        new OnPageMovedEvent(oldPath, pageToMove.getPath(), s.getUsername(), false));

      return pageToMove;
    }
    catch(RepositoryException ex)
    {
      Logger.getLogger(GidooCMS.ERROR_LOG).log(Level.SEVERE, null, ex);
      s.error(getI18N(s).getString("jcrerror"));
    }

    return null;
  }

  public static ResourceBundle getI18N(org.apache.wicket.Session session)
  {
    return java.util.ResourceBundle.getBundle("org/gidoo/cms/jcr/I18N", session.getLocale());
  }
}
